package lexico;

import automatos.Automaton;
import automatos.DelimiterAutomaton;
import automatos.IdentifierAutomaton;
import automatos.NumericAutomaton;
import automatos.OperatorAutomaton;
import automatos.StringAutomaton;
import exceptions.LexerException;
import java.util.Arrays;
import java.util.List;

/**
 * @author heekinho
 */
public class Lexico {

    private CodeBuffer buffer;
    private SymbolTable table = new SymbolTable();
    public ErrorTable eTable = new ErrorTable();
    private List<String> operators =
            Arrays.asList(new String[]{"+", "-", "*", "/", "=", ".", "!=", "<", ">",
                ">=", "<=", "&&", "||", "==", "++", "--","*"});
    private List<String> delimiters =
            Arrays.asList(new String[]{";", "(", ")", "{", "}", "[", "]"});
    private List<String> keywords =
            Arrays.asList(new String[]{"algoritmo", "variaveis", "constantes",
                "registro", "tipo", "funcao", "retorno", "vazio", "principal", "se",
                "entao", "senao", "enquanto", "para", "leia", "escreva", "inteiro",
                "real", "logico", "caractere", "verdadeiro", "falso"});


	/* Usar algo como HashTable para guardar isso mais fácilmente?
	 * Algo como Arrys com Constantes númericas para acesso por índice? */
	private Automaton delimiterAutomaton;
        private Automaton operatorAutomaton;
        private Automaton stringAutomaton;
        private Automaton identifierAutomaton;
        private Automaton numericAutomaton;

    /**
     * Realiza o processamento Léxico do código contido no buffer.
     * @param buffer
     */
    public Lexico(CodeBuffer buffer) {
        this.buffer = buffer;
		makeAutomata();

    }


	private void makeAutomata(){
		delimiterAutomaton = new DelimiterAutomaton(buffer);
                operatorAutomaton = new OperatorAutomaton(buffer);
                stringAutomaton = new StringAutomaton(buffer);
                identifierAutomaton = new IdentifierAutomaton(buffer);
                numericAutomaton = new NumericAutomaton(buffer);
		/* TODO: Colocar outros automatos */
	}

    /* Como ficou decidido na seção tutorial, o switch vai ser a partir do
     * primeiro char do possível token. É necessário guardar as informações
     * do primeiro caractere, até que a leitura de um novo token começe a ser
     * realizada. */
    private char currentChar;		// Char onde começou o token.
    private int currentLine;		// Linha deste primeiro char.
    private int currentPosition;	// Coluna deste primeiro char.

    /**
     * Realiza o scan propriamente dito do código.
     */
    public void scan() {

        table = new SymbolTable();
        eTable = new ErrorTable();
        
        while (!buffer.eof()) {

            try {
                /* Atualiza informação do primeiro caracter do próximo token.
                 * Deve ser usado posteriormente para compor o token */
                currentChar = buffer.lookAhead();
                currentLine = buffer.getLine();
                currentPosition = buffer.getPositionOnLine();
                if (Automaton.isLetra(currentChar)) {
                        //identifierAutomaton();
                        //Implementar: Se não der erro: continue.
                        table.pushBackToken(identifierAutomaton.process());
                        continue;
                }

                /* Tomar cuidado com else ifs ou breaks pois '-' também é operador */
                if (Character.isDigit(currentChar) || (currentChar == '-' && buffer.hasDoubleLookAhead() && Character.isDigit(buffer.doubleLookAhead()))) {
                        //DICA: Usar lookAhead para ver cai aki ou no próximo "operador"?

                        table.pushBackToken(numericAutomaton.process());
                        continue;
                }
                if (listContainsElementStartingWith(operators, String.valueOf(currentChar))) {
                        /*TODO: Automato de Operadores */
                        //DICA: Fazer uso constante do lookAhead pois tem operadores de 2 caracteres?

                        table.pushBackToken(operatorAutomaton.process());
                        continue;
                }
                if (currentChar == '\"') {
                        /*TODO: Fazer tratamento da exceção capturada
                        referente ao Automato de String*/
                        table.pushBackToken(stringAutomaton.process());
                        continue;
                }
                if (delimiters.contains(String.valueOf(currentChar))) {
                        table.pushBackToken(delimiterAutomaton.process());
                        continue;
                }

                //se nao pular nenhum um espaco em branco, tab ou \n, eh erro
                if (!buffer.skipUnnecessaryCode()){
                    throw new LexerException("Caractere inválido!", String.valueOf(buffer.next()), currentLine, currentPosition);
                }

            } catch (LexerException e) {
                    eTable.pushBackLexerException(e);
            }
        }
    }

    public ErrorTable getErrorTable(){
        return eTable;
    }

    /**
     * "Automato" de palavras chaves.
     * @return
     */
    public boolean isKeyword(String word) {
        if (keywords.contains(word)) {
            return true;
        }
        return false;
    }    

    /**
     * Obtém o token propriamente dito de uma palavra chave
     * @param word "lexema"
     * @return
     */
    public Token getKeywordToken(String word) {
        return new Token(TokenClass.KEYWORD, word, currentLine, currentPosition);
    }

    /**
     * A responsabilidade não é desta classe, mas é um método que ajuda bastante.
     * @param list Lista de elementos onde a busca será feita.
     * @param start String que o elemento procurado deve começar por.
     * @return true se algum elemento foi encontrado.
     */
    public static boolean listContainsElementStartingWith(List<String> list, String start) {
        for (String element : list) {
            if (element.startsWith(start)) {
                return true;
            }
        }
        return false;
    }   

    public SymbolTable getTable() {
        return table;
    }

    public CodeBuffer getBuffer() {
        return this.buffer;
    }
}
